package transaction;

import bean.User;
import org.junit.Test;
import util.JDBCUtils;

import java.lang.reflect.Field;
import java.sql.*;

/*
* 1.什么叫数据库事物：
* 事物：一组逻辑操作单元，是数据从一种状态到另一种状态
*        > 一组逻辑操作单元：一个或多个DML操作
*
* 2.事物处理的原则：
*   保证所有事务都作为一个工作单元来执行，即使出现了故障，都不能改变这种执行方式。
*   当在一个事务中执行多个操作时，要么所有的事务都被提交(commit)，那么这些修改就永久地保存下来；
*   要么数据库管理系统将放弃所作的所有修改，整个事务**回滚(rollback)**到最初状态。
*
* 3.数据一旦提交，就不可回滚
*
* 4.那些操作会导致数据的自动提交？
*       >DDL操作一旦执行，都会自动提交
*       >DML默认情况下，一旦执行，就会自动提交
*           >我们可以通过set autocommit = false的方式取消DML操作的自动提交
*       >默认在关闭连接时，会自动的提交数据
* */
public class TransactionTest {

    /*
     * ------------------未考虑数据库事物情况下的转账操作------------------
     * 针对数据表user_table来说
     * AA用户给BB用户转账100
    * update user_table set balance = balance - 100 where user = 'AA';
    * update user_table set balance = balance + 100 where user = 'BB';
    *
    * 发生错误时，AA用户已经给钱了，减去100
    * 但BB用户balance任然保持不变
    * */
    @Test
    public void testUpdate() {

        String sql1 = "update user_table set balance = balance - 100 where user = ?";
        change(sql1, "AA");

        //模拟网络异常 没有考虑事物，一旦出错不能回滚
        System.out.println(10 / 0);

        String sql2 = "update user_table set balance = balance + 100 where user = ?";
        change(sql2, "BB");
        System.out.println("转账成功!");
    }

    //通用的增删改操作------version 1.0  未考虑事物
    public int change(String sql, Object ...args) { //sql占位符的个数与可变形参的长度一样
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        try {
            //1.获取数据库的连接
            connection = JDBCUtils.getConnection();
            //2.预编译SQL语句，返回preparedStatement实例
            preparedStatement = connection.prepareStatement(sql);
            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1, args[i]);
            }
            //4.执行操作
            return preparedStatement.executeUpdate();//返回影响了几行数据
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //5.资源的关闭
            JDBCUtils.closeResourceConnection(connection, preparedStatement);
        }
        return 0;
    }

//--------------------------------------------------------------------------------------------

    /*
    * ------------------考虑数据库事物情况下的转账操作------------------
    * 连接放入外部，完成任务后再提交
    * 对DML操作来说，默认是执行就提交，所以设置
    *   connection.setAutoCommit(false) 取消自动提交
    * 程序执行后，手动提交数据
    *   connection.commit() 手动提交
    * 最后程序执行完后，再最后关闭连接的资源
    *   JDBCUtils.closeResourceConnection(connection, null) 关闭连接资源
    *
    * 发生错误时，因为没有提交数据，AA的balance不会改变
    * */
    @Test
    public void testUpdateWithTx() {
        Connection connection = null;
        try {
            //连接数据库
            connection = JDBCUtils.getConnection();
            //取消数据的自动提交
            connection.setAutoCommit(false);
            //AA转账给BB 100
            String sql1 = "update user_table set balance = balance - 100 where user = ?";
            change2(connection, sql1, "AA");
            //模拟网络异常 没有考虑事物，一旦出错不能回滚
            System.out.println(10 / 0);
            String sql2 = "update user_table set balance = balance + 100 where user = ?";
            change2(connection, sql2, "BB");
            System.out.println("转账成功!");
            //提交数据
            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //修改其为自动提交数据
            //主要针对使用数据库连接池的使用
            try {
                if (connection != null) {
                    connection.setAutoCommit(true);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
            //关闭连接资源，默认提交
            JDBCUtils.closeResourceConnection(connection, null);
        }
    }

    //通用的增删改操作------version 2.0 考虑事物
    public int change2(Connection connection, String sql, Object ...args) { //sql占位符的个数与可变形参的长度一样
        PreparedStatement preparedStatement = null;
        try {
            //1.预编译SQL语句，返回preparedStatement实例
            preparedStatement = connection.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            //3.执行操作
            return preparedStatement.executeUpdate();//返回影响了几行数据
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            //4.资源的关闭
            JDBCUtils.closeResourceConnection(null, preparedStatement);
        }
        return 0;
    }


//-----------------------------------------------------------------------------------------

    /*数据库隔离级别的展示*/
    //查询表
    @Test
    public void testTransactionSelect() {
        Connection connection = null;
        try {
            connection = JDBCUtils.getConnection();
            //获取当前连接的隔离级别
            System.out.println(connection.getTransactionIsolation());
            //设置数据库的隔离级别
            connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            //取消自动提交数据
            connection.setAutoCommit(false);

            String sql = "select user,password,balance from user_table where user=?";
            User cc = getInstance(connection, User.class, sql, "CC");
            System.out.println(cc.toString());
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeResourceConnection(connection, null);
        }
    }
    //更改表
    @Test
    public void testTransactionUpdate() {
        Connection connection = JDBCUtils.getConnection();
        String sql = "update user_table set balance = ? where user = ?";
        change2(connection, sql, 5000, "AA");
    }

    //通用的查询操作：用于返回数据表中的一条记录(version 2.0，考虑事物)
    public <T> T getInstance(Connection connection, Class<T> clazz, String sql, Object... args) {

        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //预编译SQL语句
            preparedStatement = connection.prepareStatement(sql);
            //填充占位符
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
            }
            //执行并获取结果集
            resultSet = preparedStatement.executeQuery();
            //获取结果集中列数
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            //处理数据
            if (resultSet.next()) {
                T t = clazz.newInstance();//创建某个类的对象
                for (int i = 0; i < columnCount; i++) {
                    Object value = resultSet.getObject(i + 1);//获取列的值
                    //获取表的别名getColumnLabel()  获取表的列名getColumnName()
                    String columnLabel = metaData.getColumnLabel(i + 1);
                    //给t对象指定的某个属性，赋值为value。(通过反射)
                    Field field = clazz.getDeclaredField(columnLabel);
                    field.setAccessible(true);//允许访问私有属性
                    field.set(t, value);
                }
                return t;
            }
        } catch (SQLException | IllegalAccessException | NoSuchFieldException | InstantiationException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResourceConnection(null, preparedStatement, resultSet);
        }
        return null;
    }
}
